/*
 * Copyright 2008 Slava Chernyak
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package com.chernyak.DataAdapter;

import java.util.*;

/**
 * This class is the top-level object in the AST representing a 
 * declaration of a data adapter that will be compiled to a 
 * Hadoop Writable or Writable Comparable object
 * 
 * @author Slava Chernyak
 *
 */
public class DataAdapterDecl extends ParserNode {

	String pckg;
	String name;
	DataAdapterDeclBody body;
	
	private GlobalSymbolProperties memoizedProperties;
	
	/** 
	 * Constructor used by CUP-generated parser
	 * 
	 * @param fullName
	 * @param body
	 * @param ln
	 */
	public DataAdapterDecl(String fullName, DataAdapterDeclBody body, int ln) {
		super(ln);
		String[] nameParts = fullName.split("\\|");
		if (nameParts.length != 2) {
			throw new CompilerException(ln);
		} 
		if (nameParts[0].length() != 0) {
			this.pckg = nameParts[0].substring(0, nameParts[0].length() - 1);
			this.name = nameParts[1];
		} else {
			this.pckg = null;
			this.name = nameParts[1];
		}
		this.body = body;
	}
	
	/**
	 * Returns the string representation of this data adapter declaration
	 */
	public String toString() {
		return name + body.toString();
	}
	
	/**
	 * @see ParserNode#typecheck(Map, List, String)
	 */
	public void typecheck(Map<String, GlobalSymbolProperties> gs, List<String> s, String packageContext) { 
		String fullName = (pckg != null) ? pckg + "." + name : name;
		for (String str : s) {
			if (fullName.equals(str)) {
				throw new CompilerException("Error while typechecking. \n" + 
						"Name " + fullName + " already in use.\nLine " + 
						ln + "\n", ln);
			}
		}
		
		s.add(fullName);
		memoizedProperties = 
			new GlobalSymbolProperties(body.getParsable(gs), body.isComparable());
		
		if (! memoizedProperties.isComparable()) {
			MessageSink.Warn("Cannot generate WritableComparable from data " +
					"adapter defintion '" + name + "'. Generating Writable instead.", ln);
		} 
		
		if (memoizedProperties.getParsable() == ParsableStatus.NOT_PARSABLE) {
			MessageSink.Warn("Cannot generate parsing object from data " +
					"adapter defintion '" + name + "'. Generating non-parsing object.", ln);
		}
		
		gs.put(fullName, memoizedProperties);
		body.typecheck(gs, new ArrayList<String>(), pckg);
	}
	
	/**
	 * @see ParserNode#lowerDeclarations(StringBuilder)
	 */
	public void lowerDeclarations(StringBuilder sink) {
		sink.append("private static final String __DA_STRING_DELIM = \"|\";");
		body.lowerDeclarations(sink);
	}
	
	/**
	 * Lowers the class file header for this data adapter declaration. The
	 * header contains the package and import statements, and the class 
	 * declaration. 
	 * @param sink The StringBuilder to write the output to
	 */
	public void lowerHeader(StringBuilder sink) {
		if (pckg != null) {
			sink.append("package " + pckg + ";");
		}
		sink.append("import java.io.*;");
		if (memoizedProperties.getParsable() != ParsableStatus.NOT_PARSABLE) {
		  sink.append("import java.util.*;");
		}
		if (memoizedProperties.isComparable()) {
			sink.append("import org.apache.hadoop.io.WritableComparable;");
		} else {
			sink.append("import org.apache.hadoop.io.Writable;");
		}
		sink.append("public class " + this.name + " implements ");
		if (memoizedProperties.isComparable()) {
			sink.append("WritableComparable<" + this.name + "> {");
		} else {
			sink.append("Writable {");
		}
	}
	
	/**
	 * Lowers the class file footer for this data adapter declaration. The
	 * footer contains the closing of the class body
	 * @param sink The StringBuilder to write the output to
	 */
	public void lowerFooter(StringBuilder sink) {
		sink.append("}"); // close the class
	}
	
	/**
	 * @see ParserNode#lowerEquals(StringBuilder)
	 */
	public void lowerEquals(StringBuilder sink) {
		sink.append("@Override ");
		sink.append("public boolean equals(Object o) {");
		sink.append(	"if (! (o instanceof " + this.name + ")) {");
		sink.append(		"return false;\n");
		sink.append(	"}");
		sink.append(	this.name + " __da_o = (" + this.name + ")o;");
		sink.append(	"boolean __da_test = true;");
		body.lowerEquals(sink);
		sink.append(	"return __da_test;");
		sink.append("}");
	}
	
	/**
	 * @see ParserNode#lowerToString(StringBuilder)
	 */
	public void lowerToString(StringBuilder sink) {
		sink.append("@Override ");
		sink.append("public String toString() {");
		sink.append(	"String __da_str = \"\";");
		body.lowerToString(sink);
		sink.append(	"return __da_str;");
		sink.append("}");
	}
	
	/**
	 * @see ParserNode#lowerHashCode(StringBuilder)
	 */
	public void lowerHashCode(StringBuilder sink) {
		if (this.body.isComparable()) {
			sink.append("@Override ");
			sink.append("public int hashCode() {");
			sink.append("int __da_hashcode = -1;");
			body.lowerHashCode(sink);
			sink.append("return __da_hashcode;");
			sink.append("}");
		} else {
			//TODO: hash code generation in case of no comparable
			sink.append("@Override ");
			sink.append("public int hashCode() {");
			sink.append("  return -1;");
			sink.append("}");
		}
	}
	
	/**
	 * @see ParserNode#lowerReadWCG(StringBuilder)
	 */
	public void lowerReadWCG(StringBuilder sink) {
		sink.append("public void readFields(DataInput d) throws IOException {");
		body.lowerReadWCG(sink);
		sink.append("}");
	}
	
	/**
	 * @see ParserNode#lowerWriteWCG(StringBuilder)
	 */
	public void lowerWriteWCG(StringBuilder sink) {
		sink.append("public void write(DataOutput d) throws IOException  {");
		body.lowerWriteWCG(sink);
		sink.append("}");
	}
	
	/**
	 * @see ParserNode#lowerComparisonWCG(StringBuilder)
	 */
	public void lowerComparisonWCG(StringBuilder sink) {
		if (! memoizedProperties.isComparable()) {
			return;
		}
		sink.append("public int compareTo(" + this.name + " o) {");
		sink.append(name + " __da_o = (" + name + ")o;");
		sink.append("int __da_c_result = 0;");
		body.lowerComparisonWCG(sink);
		sink.append("return __da_c_result;");
		sink.append("}");
	}
	
	/**
	 * @see ParserNode#lowerParse(StringBuilder)
	 */
	public void lowerParse(StringBuilder sink) {
		if (memoizedProperties.getParsable() == ParsableStatus.NOT_PARSABLE) { 
			return;
		}
		sink.append("public static " + this.name + " parse(String str, String fieldDelim) { ");
		sink.append(name + " __da_o = new " + name + "();");
		sink.append("StringTokenizer __da_tokens = new StringTokenizer(str, fieldDelim);");
		body.lowerParse(sink);
		sink.append("return __da_o;");
		sink.append("}");
	}
	
	/**
	 * Generate the file name for the resulting java file. The file name is the same
	 * as the name of this data adapter declaration with ".java" appended
	 * @return The file name for the resulting java file
	 */
	public String generateWritableComparableFileName() {
		return name + ".java";
	}
}
